package com.chinackts.exceptionadvice.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chinackts.common.exception.ApplicationException;
import com.chinackts.common.utils.NetworkUtil;
import com.chinackts.exceptionadvice.event.SingleWindowCollectionErrorEvent;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponseWrapper;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class ExceptionAdviceAspect implements ApplicationEventPublisherAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionAdviceAspect.class);

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Pointcut("execution(public * com.chinackts.*.controller.*.*(..))")
    public void commonAdvice() {

    }

    /**
     * 异常通知
     */
    @AfterThrowing(pointcut = "commonAdvice()", throwing = "e")
    public void doException(JoinPoint point, Exception e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (e instanceof ApplicationException) {
            LOGGER.error(String.format("● 关单采集业务异常，当前采集用户IP：%s", attributes != null ? NetworkUtil.getIpAddress(attributes.getRequest()) : StringUtils.EMPTY));
            return;
        }
        //获取到请求对象
        if (null != attributes) {
            HttpServletRequest request = attributes.getRequest();
            packageDingMessage(point, request, e);
        }
    }

    /**
     * 封装参数发ding机器人
     */
    public void packageDingMessage(JoinPoint point, HttpServletRequest request, Throwable e) {
        String strClassName = point.getTarget().getClass().getName();
        String strMethodName = point.getSignature().getName();
        Map<String, Object> paramMap = methodBefore(point);

        String markMessage = "### 单一窗口采集服务\n" +
                "* 类名\n" +
                "#### " + strClassName + "\n" +
                "* 方法名\n" +
                "#### " + strMethodName + "\n" +
                "* 请求参数" + "\n" +
                "#### " + JSON.toJSONString(paramMap) + "\n" +
                "* 请求IP\n" +
                "#### " + NetworkUtil.getIpAddress(request) + "\n" +
                "* 请求URL" + "\n" +
                "#### " + request.getRequestURL() + "\n" +
                "* HTTP method\n" +
                "#### " + request.getMethod() + "\n" +
                "* 异常信息\n" +
                "```java\n" +
                StringUtils.left(e.getMessage(), 1000) + "\n" +
                "```\n" +
                String.format("###### %s 发布  ", DateTime.now().toString("yyyy-MM-dd HH:mm:ss"));

        applicationEventPublisher.publishEvent(new SingleWindowCollectionErrorEvent(this, "单一窗口采集服务", markMessage));
    }

    /**
     * 获取参数
     */
    public Map<String, Object> methodBefore(JoinPoint joinPoint) {
        Map<String, Object> paramMap = new HashMap<>();
        // 打印请求内容
        try {
            // 下面两个数组中，参数值和参数名的个数和位置是一一对应的。
            Object[] args = joinPoint.getArgs();
            String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();

            for (int i = 0; i < args.length; i++) {
                if (!(args[i] instanceof ExtendedServletRequestDataBinder)
                        && !(args[i] instanceof HttpServletResponseWrapper)
                        && !(args[i] instanceof ServletRequest)
                        && !(args[i] instanceof ServletResponse)
                        && !(args[i] instanceof MultipartFile)) {
                    paramMap.put(argNames[i], args[i]);
                }
            }
            if (paramMap.size() > 0) {
                LOGGER.info("\n方法:{}\n参数:{}", joinPoint.getSignature(), JSONObject.toJSONString(paramMap));
            }

        } catch (Exception e) {
            LOGGER.error("AOP methodBefore:", e);
        }
        return paramMap;
    }
}
